//
//  sdl_player_1_display.c
//  FFmpegDemo
//
//  Created by xiaerfei on 2019/4/12.
//  Copyright © 2019 erfeixia. All rights reserved.
//

#include "sdl_player_1_display.h"
#include <SDL2/SDL.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <assert.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsometimes-uninitialized"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"


#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE  192000

struct SwrContext *audio_convert_ctx = NULL;

typedef struct PacketQueue {
    AVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;

PacketQueue audioq;

int quit = 0;

static void packet_queue_init(PacketQueue *q) {
    memset(q, 0, sizeof(PacketQueue));
    // 创建锁
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
}

static int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
    
    AVPacketList *pkt1;
    if(av_dup_packet(pkt) < 0) { return -1; }
    
    pkt1 = av_malloc(sizeof(AVPacketList));
    if (!pkt1) {return -1;}
    
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    // 加锁
    SDL_LockMutex(q->mutex);
    
    if (!q->last_pkt) {
        q->first_pkt = pkt1;
    } else {
        q->last_pkt->next = pkt1;
    }
    
    q->last_pkt = pkt1;
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);
    // 解锁
    SDL_UnlockMutex(q->mutex);
    return 0;
}

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
    AVPacketList *pkt1;
    int ret;
    
    SDL_LockMutex(q->mutex);
    
    for (;;) {
        if (quit) { ret = -1; break; }
        pkt1 = q->first_pkt;
        if (pkt1) {
            q->first_pkt = pkt1->next;
            if (!q->first_pkt) {
                q->last_pkt = NULL;
            }
            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            *pkt = pkt1->pkt;
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {
            ret = 0;
            break;
        } else {
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

static int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {
    
    static AVPacket pkt;
    static uint8_t *audio_pkt_data = NULL;
    static int audio_pkt_size = 0;
    static AVFrame frame;
    
    int len1, data_size = 0;
    
    for(;;) {
        while(audio_pkt_size > 0) {
            int got_frame = 0;
            /* 使用avcodec_decode_audio4函数来解码音频，但解码得到的数据类型为float4bit，
             而播放器播放的格式一般为S16（signed16bit）,就需要对解码得到的数据进行转换
             */
            len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
            if(len1 < 0) {
                /* if error, skip frame */
                audio_pkt_size = 0;
                break;
            }
            audio_pkt_data += len1;
            audio_pkt_size -= len1;
            data_size = 0;
            if(got_frame) {
                /*
                 可能是 第一个2：有两个声道
                       第二个2：每个声道有两个字节
                       采样率
                 */
                data_size = 2 * 2 * frame.nb_samples;
                
                assert(data_size <= buf_size);
                /*
                 这里是转换的开始
                 */
                swr_convert(audio_convert_ctx,
                            &audio_buf,
                            MAX_AUDIO_FRAME_SIZE*3/2,
                            (const uint8_t **)frame.data,
                            frame.nb_samples);
                
                //memcpy(audio_buf, frame.data[0], data_size);
            }
            if(data_size <= 0) {
                /* No data yet, get more frames */
                continue;
            }
            /* We have data, return it and come back for more later */
            return data_size;
        } // while 结束
        if(pkt.data)
            av_free_packet(&pkt);
        
        if(quit) {
            return -1;
        }
        
        if(packet_queue_get(&audioq, &pkt, 1) < 0) {
            return -1;
        }
        audio_pkt_data = pkt.data;
        audio_pkt_size = pkt.size;
    }
}

void audio_callback(void *userdata, Uint8 *stream, int len) {
    AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
    int len1, audio_size;
    
    static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
    static unsigned int audio_buf_size = 0;
    static unsigned int audio_buf_index = 0;
    
    while(len > 0) {
        if(audio_buf_index >= audio_buf_size) {
            /* We have already sent all our data; get more */
            audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
            if(audio_size < 0) {
                /* If error, output silence 静默音*/
                audio_buf_size = 1024; // arbitrary?
                memset(audio_buf, 0, audio_buf_size);
            } else {
                audio_buf_size = audio_size;
            }
            audio_buf_index = 0;
        }
        len1 = audio_buf_size - audio_buf_index;
        if(len1 > len) {len1 = len;}
        fprintf(stderr, "index=%d, len1=%d, len=%d\n",
                audio_buf_index,
                len,
                len1);
        memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
        len -= len1;
        stream += len1;
        audio_buf_index += len1;
    }
}

void sdl_player_1_display(void) {
    int height = 540;
    int width  = 960;
    const int video_width = 1920, video_height = 1080;
    
    int videoStream = -1, audioStream = -1;
    
    int64_t in_channel_layout;
    int64_t out_channel_layout;
    
    AVFormatContext *pFormatCtx = NULL;
    
    struct SwsContext *sws_ctx = NULL;
    
    AVPicture      *pict = NULL;
    AVFrame         *pFrame = NULL;
    AVPacket        packet;
    int             frameFinished;
    
    //for video render
    int w_width  = 640;
    int w_height = 480;
    
    //for video decode
    AVCodecContext  *pCodecCtxOrig = NULL;
    AVCodecContext  *pCodecCtx = NULL;
    AVCodec          *pCodec = NULL;
    
    //for audio decode
    AVCodecContext  *aCodecCtxOrig = NULL;
    AVCodecContext  *aCodecCtx = NULL;
    AVCodec          *aCodec = NULL;
    
    
    //for event
    SDL_Event       event;
    
    //for audio
    SDL_AudioSpec   wanted_spec, spec;
    
    /********************** 初始化 SDL 部分 ***********************/
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
    SDL_Window *window = SDL_CreateWindow("sdl diplay",
                                          SDL_WINDOWPOS_UNDEFINED,
                                          SDL_WINDOWPOS_UNDEFINED,
                                          width, height,
                                          SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    
    if (window == NULL) {
        printf("创建 window 失败 ！！！");
        goto __exit;
    }

    
    SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
    

    //code 废弃的方法
    if (renderer == NULL) {
        printf("创建 renderer 失败 ！！！");
        goto __exit;
    }
    
    SDL_Texture *texture = SDL_CreateTexture(renderer,
                                             SDL_PIXELFORMAT_IYUV,
                                             SDL_TEXTUREACCESS_STREAMING,
                                             video_width, video_height);
    if (texture == NULL) {
        printf("创建 texture 失败 ！！！");
        goto __exit;
    }
    
    /********************** 初始化 音视频 部分 ***********************/

    //code 废弃的方法
    av_register_all();
    
    const char *path = "/Users/xiaerfei/Documents/ffmpegDemo/res/cctv6live1.ts";
    
    // Open video file
    if(avformat_open_input(&pFormatCtx, path, NULL, NULL)!=0){
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open video file!");
        goto __exit; // Couldn't open file
    }
    
    // Retrieve stream information
    if(avformat_find_stream_info(pFormatCtx, NULL)<0){
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to find stream infomation!");
        goto __exit; // Couldn't find stream information
    }
    
    // Dump information about file onto standard error
    av_dump_format(pFormatCtx, 0, path, 0);
    // 找到流的索引
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
        
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStream = i;
        }
    }
    // 如果没有找到，那就退出
    if(videoStream == -1) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, " Didn't find a video stream ");
        goto __exit; // Didn't find a video stream
    }
    
    if(audioStream == -1) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, " Didn't find a audio stream ");
        goto __exit; // Didn't find a video stream
    }

    aCodecCtxOrig = pFormatCtx->streams[audioStream]->codec;
    // 找到解码器
    aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
    
    if(!aCodec) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unsupported codec! ");
        goto __exit; // Didn't find a video stream
    }
    
    // Copy context
    aCodecCtx = avcodec_alloc_context3(aCodec);
    //code 废弃的方法
    if(avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't copy codec context! ");
        goto __exit; // Didn't find a video stream
    }
    
    // Set audio settings from codec info
    wanted_spec.freq     = aCodecCtx->sample_rate;
    wanted_spec.format   = AUDIO_S16SYS;
    wanted_spec.channels = aCodecCtx->channels;
    wanted_spec.silence  = 0;
    wanted_spec.samples  = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = aCodecCtx;
    // 打开音频设备
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open audio device - %s!", SDL_GetError());
        goto __exit;
    }
    // 打开解码器
    if (avcodec_open2(aCodecCtx, aCodec, NULL) != 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open codec - %s!", SDL_GetError());
        goto __exit;
    }
    
    packet_queue_init(&audioq);
    
    in_channel_layout = av_get_default_channel_layout(aCodecCtx->channels);
    out_channel_layout = in_channel_layout; //AV_CH_LAYOUT_STEREO;
    fprintf(stderr, "in layout:%lld, out layout:%lld \n", in_channel_layout, out_channel_layout);
    
    audio_convert_ctx = swr_alloc();
    if(audio_convert_ctx){
        /* 音频格式转换
         s：现有Swr上下文（如果可用），如果不可用则为NULL
         out_ch_layout：输出通道布局（AV_CH_LAYOUT_ *）
         out_sample_fmt：输出采样格式（AV_SAMPLE_FMT_ *）。
         out_sample_rate：输出采样率（频率（Hz））
         in_ch_layout：输入通道布局（AV_CH_LAYOUT_ *）
         in_sample_fmt：输入样品格式（AV_SAMPLE_FMT_ *）。
         in_sample_rate：输入采样率（频率（Hz））
         log_offset：记录级别偏移
         log_ctx：父记录上下文，可以为NULL
         */
        swr_alloc_set_opts(audio_convert_ctx,
                           out_channel_layout,
                           AV_SAMPLE_FMT_S16,
                           aCodecCtx->sample_rate,
                           in_channel_layout,
                           aCodecCtx->sample_fmt,
                           aCodecCtx->sample_rate,
                           0,
                           NULL);
    }
    swr_init(audio_convert_ctx);
    
    SDL_PauseAudio(0);
    
    
    // Get a pointer to the codec context for the video stream
    pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
    
    // Find the decoder for the video stream
    pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
    if(pCodec == NULL) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unsupported codec!");
        goto __exit;
    }
    
    // 创建解码的上下文环境
    pCodecCtx = avcodec_alloc_context3(pCodec);
    // 这里做一个 copy
    if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to copy context of codec!");
        goto __exit;
    }
    /*
     （1）为各种结构体分配内存（通过各种av_malloc()实现）。
     （2）将输入的AVDictionary形式的选项设置到AVCodecContext。
     （3）其他一些零零碎碎的检查，比如说检查编解码器是否处于“实验”阶段。
     （4）如果是编码器，检查输入参数是否符合编码器的要求
     （5）调用AVCodec的init()初始化具体的解码器。
     */
    // 打开解码器
    if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open audio decoder!");
        goto __exit;
    }
    
    // Allocate video frame
    pFrame = av_frame_alloc();
    
    w_width  = pCodecCtx->width;
    w_height = pCodecCtx->height;
    
    fprintf(stderr, "width:%d, height:%d\n", w_width, w_height);
    
    // initialize SWS context for software scaling
    // 初始化一个SwsContext
    /* 常用的几个参数含义
     srcW：源图像的宽
     srcH：源图像的高
     srcFormat：源图像的像素格式
     dstW：目标图像的宽
     dstH：目标图像的高
     dstFormat：目标图像的像素格式
     flags：设定图像拉伸使用的算法
     */
    sws_ctx = sws_getContext(pCodecCtx->width,
                             pCodecCtx->height,
                             pCodecCtx->pix_fmt,
                             pCodecCtx->width,
                             pCodecCtx->height,
                             AV_PIX_FMT_YUV420P,
                             SWS_BILINEAR,
                             NULL,
                             NULL,
                             NULL);
    if(sws_ctx == NULL) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create swsContext!");
        goto __exit;
    }
    
    pict = (AVPicture*)malloc(sizeof(AVPicture));
    avpicture_alloc(pict, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    SDL_Rect        rect;
    // Read frames and save first five frames to disk
    while(av_read_frame(pFormatCtx, &packet) >= 0) {
        // Is this a packet from the video stream?
        if(packet.stream_index == videoStream) {
            // Decode video frame
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
            // Did we get a video frame?
            if(frameFinished) {
                SDL_Delay(20);
                /* 转换像素 Convert the image into YUV format that SDL uses
                 struct SwsContext *c: sws_getContext函数返回值
                 const uint8_t *const srcSlice[]:输入图像信息 当前处理区域的每个通道数据指针
                 const int srcStride[]:每个通道行字节数
                 int srcSliceY:起始位置 ##如果srcSliceY=0，srcSliceH=height，表示一次性处理完整个图像。这种设置是为了多线程并行，
                 int srcSliceH:处理多少行 ##例如可以创建两个线程，第一个线程处理 [0, h/2-1]行，第二个线程处理 [h/2, h-1]行。并行处理加快速度。
                 uint8_t *const dst[]:输出图像信息 输出的每个通道数据指针
                 const int dstStride[]:每个通道行字节数
                 */
                sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height,
                          pict->data, pict->linesize);
                SDL_UpdateYUVTexture(texture, NULL,
                                     pict->data[0], pict->linesize[0],// Y
                                     pict->data[1], pict->linesize[1],// U
                                     pict->data[2], pict->linesize[2]);// V
                rect.x = 0;
                rect.y = 0;
                rect.w = width;
                rect.h = height;
                
                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, NULL, &rect);
                SDL_RenderPresent(renderer);
                
                av_free_packet(&packet);
            }
        } else if (packet.stream_index == audioStream) {
            packet_queue_put(&audioq, &packet);
        } else {
            av_free_packet(&packet);
        }
        
        // Free the packet that was allocated by av_read_frame
        SDL_PollEvent(&event);
        switch(event.type) {
            case SDL_QUIT:
                quit = 1;
                goto __exit;
                break;
            case SDL_WINDOWEVENT:
                SDL_GetWindowSize(window, &width, &height);
                break;
            default:
                break;
        }
    }
    
    
__exit:
    if (renderer) {
        SDL_DestroyRenderer(renderer);
    }
    if (texture) {
        SDL_DestroyTexture(texture);
    }
    if (window) {
        SDL_DestroyWindow(window);
    }
    SDL_Quit();
}


#pragma clang diagnostic pop
